﻿'Imports <xmlns="http://ch.nicovideo.jp/community/co9320">

Imports System.Reflection

Public Class SerializableClass
    Inherits NotifyPropertyChangedBase


    Public Function Serialize(Optional ByVal name As String = "") As XElement
        If name = "" Then
            name = Me.GetType.Name
        End If

        Dim rootElement = New XElement(name)

        For Each propertyInfo In Me.GetType.GetProperties()

            If Not propertyInfo.CanWrite AndAlso _
               propertyInfo.PropertyType.IsGenericType AndAlso _
               propertyInfo.PropertyType.FullName.StartsWith("System.Collections.ObjectModel.ObservableCollection") Then
                ' ReadOnly ObservableCollection Property

                Dim collectionElement = New XElement(propertyInfo.Name)

                For Each item In DirectCast(propertyInfo.GetValue(Me, Nothing), IList)
                    If item.GetType.IsClass AndAlso _
                       Not item.GetType Is GetType(String) Then
                        Dim isSerializable = False
                        Dim itemType = item.GetType
                        Do While Not itemType.BaseType Is GetType(Object)
                            If itemType.BaseType Is GetType(SerializableClass) Then
                                isSerializable = True
                                Exit Do
                            End If
                            itemType = itemType.BaseType
                        Loop

                        If isSerializable Then
                            Dim elements = DirectCast(itemType.InvokeMember("Serialize", BindingFlags.InvokeMethod, Nothing, item, New Object() {""}), XElement)
                            collectionElement.Add(elements)
                        End If
                    Else
                        Dim element = New XElement(item.GetType.Name)
                        element.Add(item)
                        collectionElement.Add(element)
                    End If
                Next

                rootElement.Add(collectionElement)

            ElseIf propertyInfo.CanWrite Then

                If Not propertyInfo.PropertyType.IsClass OrElse _
                   propertyInfo.PropertyType Is GetType(String) Then
                    Dim element = New XElement(propertyInfo.Name)
                    element.Add(propertyInfo.GetValue(Me, Nothing))
                    rootElement.Add(element)

                ElseIf propertyInfo.PropertyType Is GetType(Uri) Then
                    Dim element = New XElement(propertyInfo.Name)
                    element.Add(propertyInfo.GetValue(Me, Nothing))
                    rootElement.Add(element)
                Else

                    Dim isSerializable = False
                    Dim itemType = propertyInfo.PropertyType
                    Do While Not itemType.BaseType Is GetType(Object)
                        If itemType.BaseType Is GetType(SerializableClass) Then
                            isSerializable = True
                            Exit Do
                        End If
                        itemType = itemType.BaseType
                    Loop

                    If isSerializable Then
                        Dim elements = DirectCast(itemType.InvokeMember("Serialize", BindingFlags.InvokeMethod, Nothing, propertyInfo.GetValue(Me, Nothing), New Object() {propertyInfo.Name}), XElement)
                        rootElement.Add(elements)

                    End If

                End If
            End If
        Next

        Return rootElement

    End Function

    Public Sub Deserialize(ByVal rootElement As XElement)


        For Each propertyInfo In Me.GetType.GetProperties()

            If Not propertyInfo.CanWrite AndAlso _
               propertyInfo.PropertyType.IsGenericType AndAlso _
               propertyInfo.PropertyType.FullName.StartsWith("System.Collections.ObjectModel.ObservableCollection") Then
                ' ReadOnly ObservableCollection Property

                Dim list = DirectCast(propertyInfo.GetValue(Me, Nothing), IList)

                Dim argType = propertyInfo.PropertyType.GetGenericArguments.First

                Dim isDeserializable = False
                Dim itemType = argType
                Do While Not itemType.BaseType Is GetType(Object)
                    If itemType.BaseType Is GetType(SerializableClass) Then
                        isDeserializable = True
                        Exit Do
                    End If
                    itemType = itemType.BaseType
                Loop


                Dim listName = propertyInfo.Name
                Dim listElements = From el In rootElement.Elements Where el.Name = listName

                If listElements.Count = 0 Then
                    Exit For
                End If

                Dim itemName = argType.Name
                Dim elements = From el In listElements.First.Elements Where el.Name = itemName

                If isDeserializable Then
                    For Each el In elements
                        Dim deserializableClass = Activator.CreateInstance(argType)
                        argType.InvokeMember("Deserialize", BindingFlags.InvokeMethod, Nothing, deserializableClass, New Object() {el})
                        list.Add(deserializableClass)
                    Next
                Else
                    For Each el In elements
                        If argType Is GetType(String) Then
                            list.Add(el.Value)
                        End If
                    Next
                End If

            ElseIf propertyInfo.CanWrite Then

                If Not propertyInfo.PropertyType.IsClass OrElse _
                   propertyInfo.PropertyType Is GetType(String) Then

                    Dim name = propertyInfo.Name
                    Dim elements = From el In rootElement.Elements Where el.Name = name
                    If elements.Count = 1 Then

                        Dim obj As Object = Nothing
                        If propertyInfo.PropertyType Is GetType(Boolean) Then
                            obj = CBool(elements.Value)
                        ElseIf propertyInfo.PropertyType Is GetType(DateTime) Then
                            obj = CDate(elements.Value)
                        ElseIf propertyInfo.PropertyType Is GetType(String) Then
                            obj = elements.Value
                        ElseIf propertyInfo.PropertyType Is GetType(Integer) Then
                            obj = CInt(elements.Value)
                        ElseIf propertyInfo.PropertyType Is GetType(Double) Then
                            obj = CDbl(elements.Value)
                        End If

                        propertyInfo.SetValue(Me, obj, BindingFlags.SetProperty, Nothing, Nothing, Globalization.CultureInfo.CurrentCulture)

                    End If

                    ElseIf propertyInfo.PropertyType Is GetType(Uri) Then

                        Dim name = propertyInfo.Name
                        Dim elements = From el In rootElement.Elements Where el.Name = name
                    If elements.Count = 1 AndAlso elements.Value <> "" Then
                        propertyInfo.SetValue(Me, New Uri(elements.Value), BindingFlags.SetProperty, Nothing, Nothing, Globalization.CultureInfo.CurrentCulture)
                    End If
                Else

                    Dim name = propertyInfo.Name
                    Dim elements = From el In rootElement.Elements Where el.Name = name

                    If elements.Count > 0 Then
                        Dim deserializableClass = Activator.CreateInstance(propertyInfo.PropertyType)
                        propertyInfo.PropertyType.InvokeMember("Deserialize", BindingFlags.InvokeMethod, Nothing, deserializableClass, New Object() {elements.First})
                        propertyInfo.SetValue(Me, deserializableClass, BindingFlags.SetProperty, Nothing, Nothing, Globalization.CultureInfo.CurrentCulture)
                    End If

                    End If
            End If
        Next

    End Sub
End Class
